﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Forms;
using ESpace;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Diagnostics;
using AMVInfoSpace;

namespace AMVCommander
{
    class Gl
    {
        public static Settings Sett;

        public readonly static String plExt = "AmvPl";

        public static String dPathBackup;
        public static String dPathHistory;
        public static String dPathPls;
        public static String dPathTools;
        public static string dPathAmvInfo;
        public static String fPathUpdateLog;
        public static String fPathSettings;
        public static String fNameComments = "Comments.txt";
        public static String dPathLogs;
        public static String fPathLog;

        public static String fPathDb;
        public static String fPathDbTags;

        public static AMVCollection DB;

        #region *** запоминание положения окна редактирования ***************************************

        public static bool frmEditAmv_bOpenFind = false;
        public static Point frmEditAmv_Pos = new Point(-1, -1);

        #endregion *** запоминание положения окна редактирования ***************************************

        public static int CmprStrL(List<String> l1, List<String> l2)
        {
            if (l1.Count == 0 && l2.Count == 0) return 0;
            if (l1.Count > l2.Count)
            {
                for (int i = 0; i < l2.Count; i++)
                {
                    int ret = l1[i].ToUpper().CompareTo(l2[i].ToUpper());
                    if (ret != 0) return ret;
                }
                return 1;
            }
            else
            {
                for (int i = 0; i < l1.Count; i++)
                {
                    int ret = l1[i].CompareTo(l2[i]);
                    if (ret != 0) return ret;
                }
                if (l1.Count < l2.Count) return -1;
                else return 0;
            }
        }

        public static int CmprMusicL(List<Music> l1, List<Music> l2)
        {
            if (l1.Count == 0 && l2.Count == 0) return 0;
            if (l1.Count > l2.Count)
            {
                for (int i = 0; i < l2.Count; i++)
                {
                    int ret = l1[i].CompareTo(l2[i]);
                    if (ret != 0) return ret;
                }
                return 1;
            }
            else
            {
                for (int i = 0; i < l1.Count; i++)
                {
                    int ret = l1[i].CompareTo(l2[i]);
                    if (ret != 0) return ret;
                }
                if (l1.Count < l2.Count) return -1;
                else return 0;
            }
        }

        public static string GetMd5Len(string fPath)
        {
            //return Comm.GetMD5(fPath, true, 1024*1024); // 1 кб
            return Comm.GetMD5InCenterFile(fPath, true, 1024 * 1024 * 9); // 9 кб
            //return Comm.GetMD5(fPath, true, 104857600); // 1 мб
            //return Comm.GetMD5(fPath, true); 
        }

        const double KBite = 1024;
        const double MBite = KBite * 1024;
        const double GBite = MBite * 1024;
        public static string GetLenString(long len)
        {
            if (len < KBite) return len.ToString() + " байт";
            else if (len < MBite) return Math.Round(len / KBite, 1).ToString() + " Кб";
            else if (len < GBite) return Math.Round(len / MBite, 1).ToString() + " Mб";

            return Math.Round(len / GBite, 1).ToString() + " Гб";
        }

        public static bool ExtIsMatch(string Ext)
        {
            string ext = Ext.ToUpper();
            return ext == ".AVI" || ext == ".MKV" || ext == ".MPG" || ext == ".WMV" ||
                ext == ".MPEG" || ext == ".FLV" || ext == ".MP4" ||
                ext == ".SWF" || ext == ".VOB" || ext == ".3GP" ||
                ext == ".DIVX" || ext == ".ASF" || ext == ".MOV";
        }

        #region *** для тегов **************************************

        const int ActVerTagDb = 0;

        public static DBItem LoadRec(EStream st, string ext, DBItem Parent)
        {
            switch (ext.ToUpper())
            {
                case "": return new TagDIR(st, (TagDIR)Parent);
                case "TAG": return new TAG(st, (TagDIR)Parent);
            }
            return null;
        }

        public static TagDIR TagDB;
        public static void LoadTagDB()
        {
            if (!File.Exists(fPathDbTags)) { TagDB = new TagDIR("", null); return; }
            EStream st = new EStream(fPathDbTags);
            st.OpenLoad();

            int LoadVer = st.LoadInt();
            switch (LoadVer)
            {
                case 0: LoadTagDB0(st); break;
                default: throw new Exception("Невозможно загрузить базу данных тегов, так как версия не поддерживается");
            }

            st.Close();
        }

        static void LoadTagDB0(EStream st)
        {
            TagDB = new TagDIR(st, null);
        }

        public static void SaveTagDB()
        {
            EStream st = new EStream(fPathDbTags);
            st.OpenSave();

            st.SaveInt(ActVerTagDb);
            TagDB.Save(st);

            st.Close();
        }

        /// <summary>Синхронизация базы тегов с реальными тегами в базе записей.
        /// если тега больше нет, удаление из базы тегов.
        /// если есть тег, котого нет в базе тегов, добаелние тега в базу тегов</summary>
        public static void SincTagDirs()
        {
            List<string> realTagL = DB.GetAllTags();
            List<DBItem> tagL = TagDB.GetAllRecords(new string[1] { "TAG" });

            bool bReSave = false;

            // пробегаемся по всем тегам
            foreach (string tag in realTagL)
            {
                // проверяем, если нет, добавляем
                bool bAdd = true;
                for (int i = 0; i < tagL.Count; i++) if (tag == tagL[i].Name) { bAdd = false; break; }
                if (bAdd)
                {
                    TagDB.Items.Add(new TAG(tag, TagDB));
                    bReSave = true;
                }
            }

            tagL = TagDB.GetAllRecords(new string[1] { "TAG" });

            // пробегаемся по всем тегам в каталоге. Если есть теги, которых реально уже нет, удаляем их
            for (int i = 0; i < tagL.Count; i++)
            {
                if (!realTagL.Contains(tagL[i].Name))
                {
                    tagL[i].Parent.Items.Remove(tagL[i]);
                    bReSave = true;
                }
            }

            if (bReSave) Gl.SaveTagDB();

        }

        #endregion *** для тегов **************************************

        public static void OpenAmv(AMV amv)
        {
            if (!File.Exists(amv.FullPath)) { MessageBox.Show("Не найден файл. Обновите домашнюю директорию"); return; }
            System.Diagnostics.Process.Start(amv.FullPath);
        }

        public static void OpenInTotalCommander(string FPath)
        {
            if (File.Exists("C:\\Program Files\\Total Commander\\Totalcmd.exe")) { Process.Start("C:\\Program Files\\Total Commander\\Totalcmd.exe", "/O /R=\"" + FPath + "\""); return; }
            if (File.Exists("C:\\Program Files (x86)\\Total Commander\\Totalcmd.exe")) { Process.Start("C:\\Program Files (x86)\\Total Commander\\Totalcmd.exe", "/O /R=\"" + FPath + "\""); return; }
            MessageBox.Show("Не найден путь к TotalCommander'у");
        }

        public static void OpenOnAMVNews(int IndAmvOnSite)
        {
            try
            {
                System.Diagnostics.Process.Start("http://amvnews.ru/index.php?go=Files&in=view&id=" + IndAmvOnSite.ToString());
            }
            catch (Exception ex)
            {
                MessageBox.Show("Не удалось открыть");
                Gl.WriteLog("ОШИБКА OpenOnAMVNews", ex);
            }
        }

        public static void OpenOnAkross(int IndAmvOnSite)
        {
            try
            {
                System.Diagnostics.Process.Start("http://www.akross.ru/index.cgi?act=video;id=" + IndAmvOnSite.ToString());
            }
            catch (Exception ex)
            {
                MessageBox.Show("Не удалось открыть");
                Gl.WriteLog("ОШИБКА OpenOnAkross", ex);
            }
        }

        public static void OpenOnCreaCon(int IndAmvOnSite)
        {
            try
            {
                System.Diagnostics.Process.Start("http://creaspace.ru/videos/video.php?video_id=" + IndAmvOnSite.ToString());
            }
            catch (Exception ex)
            {
                MessageBox.Show("Не удалось открыть");
                Gl.WriteLog("ОШИБКА OpenOnCreaCon", ex);
            }
        }

        public static List<AMVInfo> infoL = new List<AMVInfo>();

        public static void LoadAmvInfoList()
        {
            infoL.Clear();
            if (!Directory.Exists(Gl.dPathAmvInfo)) return;
            string[] Fl = Directory.GetFiles(Gl.dPathAmvInfo, "*.aInf");

            foreach (string fPath in Fl)
            {
                List<AMVInfo> ainfList = AmvHtmlParser.LoadAmvInfoList(fPath);
                foreach (AMVInfo ainf in ainfList)
                {
                    // добавление только если такого номера для этого сайта еще нет
                    bool bAdd = true;
                    foreach (AMVInfo inf in infoL) if (inf.Number == ainf.Number && inf.Site == ainf.Site) { bAdd = false; break; }
                    if (bAdd) infoL.Add(ainf);
                }
            }
        }

        public static void DeleteFileInRecycle(string fPath)
        {
            RecycleBin.MoveTo(fPath);
        }

        public static void WriteLog(string caption, Exception ex)
        {
            WriteLog(caption, "", ex);
        }

        public static void WriteLog(string Caption, string Text)
        {
            WriteLog(Caption, Text, null);
        }

        public static void WriteLog(string Caption)
        {
            WriteLog(Caption, "", null);
        }

        public static void WriteLog(string caption, string text, Exception ex)
        {
            if (Sett.WRITE_LOG)
            {
                StringBuilder sb = new StringBuilder();
                sb.AppendLine("--- [" + DateTime.Now + "] " + caption + " -----------------------------------");
                if (text != "") sb.AppendLine(text);
                if (ex != null)
                {
                    sb.AppendLine("Message: " + ex.Message);
                    sb.AppendLine("StackTrace:" + ex.StackTrace);
                    sb.AppendLine();
                    sb.AppendLine();
                }
                File.AppendAllText(fPathLog, sb.ToString());
                if (Sett.DEBUG_MODE && ex != null) MessageBox.Show(sb.ToString());
            }
        }

    }

    public static class RecycleBin
    {
        private const int FO_DELETE = 3;
        private const int FOF_ALLOWUNDO = 64;
        private const int FOF_NOCONFIRMATION = 16;

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto, Pack = 1)]
        private struct SHFILEOPSTRUCT
        {
            public IntPtr hwnd;
            [MarshalAs(UnmanagedType.U4)]
            public int wFunc;
            public string pFrom;
            public string pTo;
            public short fFlags;
            [MarshalAs(UnmanagedType.Bool)]
            public bool fAnyOperationsAborted;
            public IntPtr hNameMappings;
            public string lpszProgressTitle;
        }

        [DllImport("shell32.dll", CharSet = CharSet.Auto)]
        private static extern int SHFileOperation(ref SHFILEOPSTRUCT fileOp);

        public static int MoveTo(string path)
        {
            SHFILEOPSTRUCT fop = new SHFILEOPSTRUCT();
            fop.wFunc = FO_DELETE;
            fop.pFrom = path + "\0"; // без дополнительного \0 не работает
            fop.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;

            return SHFileOperation(ref fop);
        }
    }

    public enum Action { None, AddAmv, EditAmv, DelAmv, DelNotEx, DellAll, Update, NewHome, Restore, EditTags, DelTagInAllRecs, AddTag, DeleteTag, EditKeyWords, UpdateRatings, LinkToAMVNews }

    class Backup : IComparable<Backup>
    {
        public string fPath;
        public DateTime dt;
        public Action act;
        public string Comment;
        public Backup(string fPath, DateTime dt, Action act, string Comment)
        {
            this.fPath = fPath;
            this.dt = dt;
            this.act = act;
            this.Comment = Comment;
        }

        public int CompareTo(Backup bk)
        {
            return dt.CompareTo(bk.dt) * -1;
        }

        // AMV_ticks_индексДействия.Backup
        public static bool TryParse(string fPath, Dictionary<string, string> Comments, out Backup bk)
        {
            bk = null;
            string[] sl = Comm.GetFileName(fPath).Split(new char[1] { '_' }, 3);
            if (sl.Length != 3) return false;
            long n;
            if (!long.TryParse(sl[1], out n)) return false;
            int len = Enum.GetNames(typeof(Action)).Length;
            int bkupTypeNumber;
            if (!int.TryParse(sl[2], out bkupTypeNumber)) return false;
            if (bkupTypeNumber < 0 || bkupTypeNumber >= len) return false;

            FileInfo fi = new FileInfo(fPath);

            // коментарий
            string Comment = "";
            if (Comments.ContainsKey(fi.Name)) Comment = Comments[fi.Name];

            bk = new Backup(fPath, new DateTime(n), (Action)bkupTypeNumber, Comment);
            return true;
        }

        public static void SaveBackup(Action act)
        {
            SaveBackup(act, "");
        }

        public static void SaveBackup(Action act, string Comment)
        {
            if (File.Exists(Gl.fPathDb))
            {
                if (act == Action.None)
                {
                    DelFl(Gl.dPathBackup, 29);
                    File.Copy(Gl.fPathDb, Gl.dPathBackup + "AMV_" + DateTime.Now.Ticks.ToString() + "_" + ((int)act).ToString() + ".backup");
                }
                else
                {
                    DelFl(Gl.dPathHistory, 99);
                    DeleteNotExistComments();
                    string fName = "AMV_" + DateTime.Now.Ticks.ToString() + "_" + ((int)act).ToString() + ".backup";
                    File.Copy(Gl.fPathDb, Gl.dPathHistory + fName);
                    if (Comment != "")
                    {
                        File.AppendAllText(Gl.dPathHistory + Gl.fNameComments, fName + " = " + Comment + "\r\n", Encoding.UTF8);
                    }
                }

            }
        }

        static void DelFl(string dPath, int max)
        {
            String[] fl = Directory.GetFiles(dPath, "*.backup");
            List<string> fL = new List<string>();
            for (int i = 0; i < fl.Length; i++) fL.Add(fl[i]);
            fL.Sort();
            while (fL.Count > max)
            {
                File.Delete(fL[0]);
                fL.RemoveAt(0);
            }
        }

        /// <summary> Удаление всех коментариев, бекапы к которым уже не существуют </summary>
        static void DeleteNotExistComments()
        {
            string[] fl = Directory.GetFiles(Gl.dPathHistory, "*.backup");
            string[] strL = new string[0];
            if (File.Exists(Gl.dPathHistory + Gl.fNameComments))
            {
                strL = File.ReadAllLines(Gl.dPathHistory + Gl.fNameComments, Encoding.UTF8);
                File.Delete(Gl.dPathHistory + Gl.fNameComments);
            }

            foreach (string str in strL)
            {
                string[] sl = str.Split(new string[1] { " = " }, 2, StringSplitOptions.None);
                if (sl.Length != 2) continue;

                bool bExist = false;
                foreach (string fPath in fl) if (fPath == Gl.dPathHistory + sl[0]) { bExist = true; break; }
                if (bExist) File.AppendAllText(Gl.dPathHistory + Gl.fNameComments, sl[0] + " = " + sl[1] + "\r\n", Encoding.UTF8);
            }
        }

        public static bool TodayBackupExsist()
        {
            List<Backup> bkp = new List<Backup>();
            String[] fl = Directory.GetFiles(Gl.dPathBackup, "*.backup");
            for (int i = 0; i < fl.Length; i++)
            {
                Backup bk; if (!Backup.TryParse(fl[i], new Dictionary<string, string>(), out bk)) continue;
                bkp.Add(bk);
            }
            bkp.Sort();
            if (bkp.Count == 0) return false;
            if (bkp[0].dt.Date == DateTime.Today) return true;
            return false;
        }
    }

    public class PlayListItem
    {
        public string MD5Len;
        public string AddName;
        public PlayListItem(string MD5, string AddName)
        {
            this.MD5Len = MD5;
            this.AddName = AddName;
        }
    }

    public class PlayList : IComparable<PlayList>
    {
        public string Name; // имя файла

        public List<PlayListItem> Itm = new List<PlayListItem>();
        public string FPath
        {
            get
            {
                return Gl.dPathPls + Name + "." + Gl.plExt;
            }
        }

        public PlayList(string Name, bool NameIsAFilePath)
        {
            this.Name = Name;
            if (NameIsAFilePath)
            {
                this.Name = Comm.GetFileName(Name);
                EStream st = new EStream(FPath);
                st.OpenLoad();
                int kolvo = st.LoadInt();
                for (int i = 0; i < kolvo; i++)
                    Itm.Add(new PlayListItem(st.LoadStr(), st.LoadStr()));
                st.Close();
            }
        }

        public int CompareTo(PlayList pl) { return Name.CompareTo(pl.Name); }

        public void Save()
        {
            EStream st = new EStream(FPath);
            st.OpenSave();
            st.SaveInt(Itm.Count);
            for (int i = 0; i < Itm.Count; i++)
            {
                st.SaveStr(Itm[i].MD5Len);
                st.SaveStr(Itm[i].AddName);
            }
            st.Close();
        }

        public void Delete()
        {
            if (Name == "") throw new Exception("Невозможно удалить файл, так как не указан путь к нему");
            File.Delete(FPath);
        }

        public override string ToString()
        {
            return Name.ToString();
        }

    }

}
